package cn.winwintech.Incense.dao.persist;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.LockModeType;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;

import org.springframework.stereotype.Component;

import com.base.webapp.persist.Persistence;

@Component
public class PersistenceImpl implements Persistence {

	@PersistenceContext
	private EntityManager entityManager;

	private boolean pessimisticLock;

	public EntityManager getEntityManager() {
		return entityManager;
	}

	public EntityTransaction transaction() {
		return entityManager.getTransaction();
	}

	public void close() {
		if (entityManager.isOpen()) {
			entityManager.close();
		}
	}
	@Override
	public void openPessimisticLock() {
		pessimisticLock = true;
	}

	@Override
	public void closePessimisticLock() {
		pessimisticLock = false;
	}

	@Override
	public <T> List<T> find(Class<T> modelClass) {
		String sql = "select m from " + modelClass.getName() + " m";
		TypedQuery<T> query = entityManager.createQuery(sql, modelClass);
		return updateQuery(query).getResultList();
	}

	@Override
	public <T> T find(Class<T> modelClass, Object primaryKey) {
		if (pessimisticLock) {
			return entityManager.find(modelClass, primaryKey, LockModeType.PESSIMISTIC_FORCE_INCREMENT);
		} else {
			return entityManager.find(modelClass, primaryKey);
		}
	}

	@Override
	public <T> List<T> find(Class<T> modelClass, String fieldName, Object fieldValue) {
		String sql = "select m from " + modelClass.getName() + " m where m." + fieldName + "= :value ";
		TypedQuery<T> query = entityManager.createQuery(sql, modelClass);
		query.setParameter("value", fieldValue);
		return updateQuery(query).getResultList();
	}

	public <T> List<T> findPage(Class<T> modelClass, String fieldName, Object fieldValue, String orderField,
			boolean asceding, Integer pageStart, Integer pageSize) {
		String sql = "select m from " + modelClass.getName() + " m where m." + fieldName + "= :value ";
		if (orderField != null) {
			sql += " order by " + orderField + (asceding ? " asc" : " desc");
		}
		TypedQuery<T> query = entityManager.createQuery(sql, modelClass);
		query.setParameter("value", fieldValue);
		if (pageStart != null) {
			query.setFirstResult(pageStart);
		}
		if (pageSize != null) {
			query.setMaxResults(pageSize);
		}
		return updateQuery(query).getResultList();
	}

	@Override
	public <T> List<T> findLike(Class<T> modelClass, String fieldName, Object likeValue) {
		String sql = "select m from " + modelClass.getName() + " m where m." + fieldName + " like :value ";
		TypedQuery<T> query = entityManager.createQuery(sql, modelClass);
		query.setParameter("value", likeValue);
		return updateQuery(query).getResultList();
	}

	@Override
	public <T> List<T> find(Class<T> modelClass, Map<String, Object> feildValues,
			Map<String, Boolean> feildAscendings) {
		String sql = "select m from " + modelClass.getName() + " m";
		TypedQuery<T> query = genetateQuery(modelClass, feildValues, feildAscendings, sql);
		return updateQuery(query).getResultList();
	}

	@Override
	public <T> T findSingle(Class<T> modelClass, String fieldName, Object fieldValue) {
		List<T> objlist = find(modelClass, fieldName, fieldValue);
		return (objlist == null || objlist.size() == 0) ? null : objlist.get(0);
	}

	@Override
	public <T> T findSingle(Class<T> modelClass, Map<String, Object> feildValues) {
		List<T> objlist = find(modelClass, feildValues, null);
		return (objlist == null || objlist.size() == 0) ? null : objlist.get(0);
	}

	@Override
	public <T> T findLastest(Class<T> modelClass, String timeFieldName, String fieldName, Object fieldValue) {
		Map<String, Object> feildValues = new HashMap<String, Object>();
		Map<String, Boolean> feildAscendings = new HashMap<String, Boolean>();
		feildValues.put(fieldName, fieldValue);
		feildAscendings.put(timeFieldName, false);
		List<T> objlist = find(modelClass, feildValues, feildAscendings);
		return (objlist == null || objlist.size() == 0) ? null : objlist.get(0);
	}

	@Override
	public <T> boolean exist(Class<T> modelClass, String fieldName, Object fieldValue) {
		return numberOf(modelClass, fieldName, fieldValue) > 0;
	}

	@Override
	public <T> boolean exist(Class<T> modelClass, Map<String, Object> feildValues) {
		return numberOf(modelClass, feildValues) > 0;
	}

	@Override
	public void save(Object model) {
		entityManager.persist(model);
	}

	@Override
	public <T> long numberOf(Class<T> modelClass, String fieldName, Object fieldValue) {
		String sql = "select count(m) from " + modelClass.getName() + " m where m." + fieldName + "=:value";
		TypedQuery<Long> query = entityManager.createQuery(sql, Long.class);
		query.setParameter("value", fieldValue);
		// / FIXME
		return query.getSingleResult();
	}

	@Override
	public <T> long numberOf(Class<T> modelClass, Map<String, Object> feildValues) {
		String sql = "select count(m) from " + modelClass.getName() + " m";
		TypedQuery<Long> query = genetateQuery(Long.class, feildValues, null, sql);
		return query.getSingleResult();
	}

	@Override
	public <T> Long numberBySqlOf(String sql, Object... args) {
		TypedQuery<Long> query = entityManager.createQuery(sql, Long.class);
		if (args.length > 0) {
			for (int pos = 0; pos < args.length; pos++) {
				query.setParameter(pos + 1, args[pos]);
			}
		}
		return query.getSingleResult();
	}

	@Override
	public <T> List<T> findBySql(Class<T> modelClass, String sql, Object... args) {
		return findBySqlWithPage(modelClass, null, null, sql, args);
	}

	@SuppressWarnings("unchecked")
	public <T> List<T> findByNativeSql(Class<T> modelClass, String sql, Object... args) {
		Query nativeQuery = null;
		if (modelClass != null) {
			nativeQuery = entityManager.createNativeQuery(sql, modelClass);
		} else {
			nativeQuery = entityManager.createNativeQuery(sql);
		}
		if (args != null && args.length > 0) {
			for (int pos = 0; pos < args.length; pos++) {
				nativeQuery.setParameter(pos + 1, args[pos]);
			}
		}
		if (pessimisticLock) {
			nativeQuery.setLockMode(LockModeType.PESSIMISTIC_FORCE_INCREMENT);
		}
		return nativeQuery.getResultList();
	}

	public <T> List<T> findByNativeSql(String sql, Object... args) {
		return findByNativeSql(null, sql, args);
	}

	public <T> T findSingleByNativeSql(Class<T> modelClass, String sql, Object... args) {
		List<T> objlist = findByNativeSql(modelClass, sql, args);
		return (objlist == null || objlist.size() == 0) ? null : objlist.get(0);
	}

	public <T> T findSingleByNativeSql(String sql, Object... args) {
		List<T> objlist = findByNativeSql(sql, args);
		return (objlist == null || objlist.size() == 0) ? null : objlist.get(0);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> List<T> findLimitRows(String sql, int rowsNumLimit, Object... args) {
		Query query = entityManager.createQuery(sql);
		if (args.length > 0) {
			// FIXME 冒号或等于号
			String[] split = sql.split(" ");
			List<String> argNames = new ArrayList<String>(5);
			for (String str : split) {
				if (str != null && str.startsWith(":")) {
					argNames.add(str.substring(1));
				}
			}
			if (argNames.size() != args.length) {
				throw new RuntimeException("placeholders not match with args when calling the method "
						+ "[<T> List<T> findBySql(Class<T> modelClass, String sql, Object... args)] !");
			}
			for (int i = 0; i < args.length; i++) {
				query.setParameter(argNames.get(i), args[i]);
			}
		}
		if (pessimisticLock) {
			query.setLockMode(LockModeType.PESSIMISTIC_FORCE_INCREMENT);
		}
		return query.setMaxResults(rowsNumLimit).getResultList();
	}

	@Override
	public <T> List<T> findBySqlWithMap(Class<T> modelClass, String sql, Map<String, Object> args) {
		TypedQuery<T> query = entityManager.createQuery(sql, modelClass);
		Iterator<Entry<String, Object>> it = args.entrySet().iterator();
		while (it.hasNext()) {
			Entry<String, Object> entry = it.next();
			query.setParameter(entry.getKey(), entry.getValue());
		}
		return updateQuery(query).getResultList();
	}

	private <T> TypedQuery<T> genetateQuery(Class<T> modelClass, Map<String, Object> feildValues,
			Map<String, Boolean> feildAscendings, String sql) {
		Set<String> keySet = null;
		Map<String, Object> valueMap = new HashMap<String, Object>();
		if (feildValues != null) {
			if (feildValues.size() > 0) {
				sql += " where";
			}
			keySet = feildValues.keySet();
			Iterator<String> it = keySet.iterator();
			boolean first = true;
			int index = 0;
			while (it.hasNext()) {
				String feildName = it.next();
				String key = "value" + index;
				valueMap.put(key, feildValues.get(feildName));
				if (first) {
					sql += " m." + feildName + "=:" + key;
					first = false;
				} else {
					sql += " and m." + feildName + "=:" + key;
				}
				index++;
			}
		}
		if (feildAscendings != null) {
			Iterator<Entry<String, Boolean>> entryIt = feildAscendings.entrySet().iterator();
			boolean first = true;
			while (entryIt.hasNext()) {
				Entry<String, Boolean> entry = entryIt.next();
				if (first) {
					sql += " order by " + entry.getKey() + (entry.getValue() ? " asc" : " desc");
					first = false;
				} else {
					sql += " , " + entry.getKey() + (entry.getValue() ? " asc" : " desc");
				}
			}
		}
		TypedQuery<T> query = entityManager.createQuery(sql, modelClass);
		if (valueMap != null) {
			Iterator<String> it = valueMap.keySet().iterator();
			while (it.hasNext()) {
				String key = it.next();
				query.setParameter(key, valueMap.get(key));

			}
		}
		return query;
	}

	public boolean delete(Object model) {
		entityManager.remove(model);
		return true;
	}

	@Override
	public <T> boolean delete(Class<T> modelClass, String fieldName, Object fieldValue) {
		String sql = "delete from " + modelClass.getName() + " where " + fieldName + " = :value";
		Query query = entityManager.createQuery(sql);
		query.setParameter("value", fieldValue);
		return query.executeUpdate() > 0;
	}

	@Override
	public <T> boolean delete(Class<T> modelClass, Object primaryKey) {
		String sql = "delete from " + modelClass.getName() + " where id = :id";
		Query query = entityManager.createQuery(sql);
		query.setParameter("id", primaryKey);
		return query.executeUpdate() > 0;
	}

	@Override
	public <T> T findSingleBySql(Class<T> modelClass, String sql, Object... args) {
		List<T> objlist = findBySql(modelClass, sql, args);
		return (objlist == null || objlist.size() == 0) ? null : objlist.get(0);
	}

	@Override
	public <T> List<T> findOrder(Class<T> modelClass, String feildName, boolean ascending) {
		Map<String, Boolean> feildAscendings = new HashMap<String, Boolean>();
		feildAscendings.put(feildName, ascending);
		return find(modelClass, new HashMap<String, Object>(), feildAscendings);
	}

	@Override
	public <T> List<T> find(Class<T> modelClass, Map<String, Object> feildValues, Map<String, Boolean> feildAscendings,
			Integer limitSize) {
		return find(modelClass, feildValues, feildAscendings, null, limitSize);
	}

	@Override
	public <T> List<T> find(Class<T> modelClass, Map<String, Object> feildValues, Map<String, Boolean> feildAscendings,
			Integer pageStart, Integer pageSize) {
		String sql = "select m from " + modelClass.getName() + " m";
		TypedQuery<T> query = genetateQuery(modelClass, feildValues, feildAscendings, sql);
		if (pageStart != null) {
			query.setFirstResult(pageStart);
		}
		if (pageSize != null) {
			query.setMaxResults(pageSize);
		}
		return updateQuery(query).getResultList();
	}

	@Override
	public <T> List<T> findBySql(String sql, Object... args) {
		return findBySqL(null, null, sql, args);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> List<T> findBySqL(Integer start, Integer size, String sql, Object... args) {
		Query query = entityManager.createQuery(sql);
		if (args.length > 0) {
			for (int pos = 0; pos < args.length; pos++) {
				query.setParameter(pos + 1, args[pos]);
			}
		}
		if (start != null) {
			query.setFirstResult(start);
		}
		if (size != null) {
			query.setMaxResults(size);
		}
		if (pessimisticLock) {
			query.setLockMode(LockModeType.PESSIMISTIC_FORCE_INCREMENT);
		}
		return query.getResultList();
	}

	@Override
	public <T> List<T> findBySqlWithPage(Class<T> modelClass, Integer pageStart, Integer pageSize, String sql,
			Object... args) {
		TypedQuery<T> query = entityManager.createQuery(sql, modelClass);
		if (args.length > 0) {
			for (int pos = 0; pos < args.length; pos++) {
				query.setParameter(pos + 1, args[pos]);
			}
		}
		if (pageStart != null) {
			query.setFirstResult(pageStart);
		}
		if (pageSize != null) {
			query.setMaxResults(pageSize);
		}
		return updateQuery(query).getResultList();
	}

	@Override
	public <T> T findLastest(Class<T> modelClass, String timeFieldName, Map<String, Object> feildValues) {
		Map<String, Boolean> feildAscendings = new HashMap<String, Boolean>();
		feildAscendings.put(timeFieldName, false);
		List<T> objlist = find(modelClass, feildValues, feildAscendings);
		return (objlist == null || objlist.size() == 0) ? null : objlist.get(0);
	}

	@Override
	public void detatch(Object o) {
		entityManager.detach(o);
	}

	private <T> TypedQuery<T> updateQuery(TypedQuery<T> query) {
		if (pessimisticLock) {
			query.setLockMode(LockModeType.PESSIMISTIC_FORCE_INCREMENT);
		}
		return query;
	}

	@Override
	public <T> List<T> findPage(Class<T> modelClass, String fieldName, Object fieldValue, Integer pageStart,
			Integer pageSize) {
		return findPage(modelClass, fieldName, fieldValue, null, false, pageStart, pageSize);
	}

}
